#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "firewallConfig.h"

#define u32time(timestamp) \
((u32) (((timestamp) & 0xffffffff00000000) >> 32)), \
((u32) ((timestamp) & 0xffffffff))

int* checkExpired(int* lines, int mode)
{
    char* filename;
    int LEN;
    int offset;
    int* totalLines;

    if (mode)
    {
        filename = cfgname;
        LEN = LEN_CFG_LINE;
        offset = 8;
        totalLines = &totalIPLines;
    }
    else
    {
        filename = portcfgname;
        LEN = LEN_PORT_LINE;
        offset = 4;
        totalLines = &totalPtLines;
    }

    FILE * fp = fopen(filename, "r");
    if (fp)
    {
        int currentLine = 1;

        // 当前实际
        time_t now = time(NULL);
        // 用于转换时间戳的中间变量
        u32 temp = 0;
        // 用于存储超时的行数
        int cnt = 0;

        // 跳过表头和第一个 IP 或 Port（+2 是因为 '\n' 占两个字节）
        fseek(fp, (2 * LEN + offset + 2) * sizeof(char), SEEK_SET);
        while (currentLine <= *totalLines)
        {
            // 配置写入时间
            time_t timestamp = 0;
            fscanf(fp, "%2x", &temp);
            timestamp += temp;
            timestamp <<= 32;
            fscanf(fp, "%8x", &temp);
            timestamp += temp;

            if (now - timestamp >= TTL)
            {
                *(lines + cnt + 1) = currentLine;
                // ---------------------------------------
                printf("Expired: Line %d\n", currentLine);
                // ---------------------------------------
                cnt++;
            }

            fseek(fp, (LEN - 9) * sizeof(char), SEEK_CUR);
            currentLine++;
        }

        *lines = cnt;
        fclose(fp);
    }
    else
        printf("Check Expired Items Failed!\n");

    return lines;
}

bool deleteExpired(int* lines, int mode)
{
    bool ret = false;
    int total = *lines;

    char* filename;
    int LEN;
    int* totalLines;

    if (mode)
    {
        filename = cfgname;
        LEN = LEN_CFG_LINE;
        totalLines = &totalIPLines;
    }
    else
    {
        filename = portcfgname;
        LEN = LEN_PORT_LINE;
        totalLines = &totalPtLines;
    }

    if (total)
    {
        int cnt = 0;
        int currentLine = -1;
        FILE * fpo = fopen(filename, "r");
        if (fpo)
        {
            FILE * fpn = fopen(tmpname, "w");
            if (fpn)
            {
                char* buff = (char*)malloc((LEN + 1) * sizeof(char));
                while (currentLine <= *totalLines)
                {
                    if (currentLine == *(lines + cnt + 1) && cnt <= total)
                    {
                        currentLine++;
                        cnt++;
                        fseek(fpo, (LEN + 1) * sizeof(char), SEEK_CUR);
                        continue;
                    }
                    buff = fgets(buff, LEN + 1, fpo);
                    fprintf(fpn, "%s", buff);
                    currentLine++;
                }
                free(buff);
                fclose(fpn);
                ret = true;
            }
            else
                printf("Create Temp File Failed!\n");
            fclose(fpo);
        }
        else
            printf("Open File Failed!\n");
        if (ret)
        {
            remove(filename);
            rename(tmpname, filename);
            *totalLines -= *lines;
            printf("Deleted Expired Items!\n");
        }
    }

    return ret;
}

bool refreshCfg(int mode)
{
    int* totalLines;

    if (mode)
    {
        totalLines = &totalIPLines;
        printf("Refreshing IP Config File...\n");
    }
    else
    {
        totalLines = &totalPtLines;
        printf("Refreshing Port Config File...\n");
    }

    int* lines = (int*)malloc((*totalLines + 1) * sizeof(int));
    bool ret = deleteExpired(checkExpired(lines, mode), mode);
    free(lines);

    return ret;
}

int searchItem(u32 num, char type, int mode)
{
    int ret = 0;

    char* filename;
    int LEN;
    int* totalLines;
    int offset;
    u32 mask;

    if (mode)
    {
        filename = cfgname;
        LEN = LEN_CFG_LINE;
        totalLines = &totalIPLines;
        offset = 8;
        mask = 0xffffffff;
    }
    else
    {
        filename = portcfgname;
        LEN = LEN_PORT_LINE;
        totalLines = &totalPtLines;
        offset = 4;
        mask = 0xffff;
    }

    FILE * fp = fopen(filename, "r");
    if (fp)
    {
        int currentLine = 1;
        fseek(fp, (2 * LEN + 2) * sizeof(char), SEEK_SET);
        u32* numbuff = (u32*)malloc(sizeof(u32));
        char * buff = (char *) malloc((LEN - offset + 1) * sizeof(char));
        char str[4];
        sprintf(str, "%%%dx", offset);
        while (currentLine <= *totalLines)
        {
            fscanf(fp, str, numbuff);
            fgets(buff, LEN - offset + 1, fp);
            // printf("%d\n", *numbuff);
            // printf("%s\n", buff);
            if (type == *(buff + 10) && ((num & mask) == (*numbuff & mask)))
            {
                ret = currentLine;
                break;
            }
            currentLine++;
        }
        free(numbuff);
        free(buff);
        fclose(fp);
    }
    else
        printf("Open Config File Failed!\n");

    return ret;
}

bool checkStatus(unsigned int item, char type, int mode)
{
    bool ret = false;

    int offset;
    char* filename;
    int LEN;

    if (mode)
    {
        offset = type == 's' ? 18 : 30;
        filename = protocolcfgname;
        LEN = LEN_PROTOCOL_LINE;
    }
    else
    {
        offset = type == 's' ? 14 : 26;
        filename = netcardcfgname;
        LEN = LEN_NETCARD_LINE;
    }

    FILE * fp = fopen(filename, "r");
    if (fp)
    {
        fseek(fp, ((item + 1) * (LEN + 1) + offset) * sizeof(char), SEEK_SET);
        if (fgetc(fp) == 'B')
            ret = true;
        fclose(fp);
    }
    else
        printf("Open File Failed!\n");
}

int checkBanned(int amount, Package* oriIPsets, Package* IPsets)
{
    int cnt = 0;
    for (int i = 0; i < amount; i++)
    {
        if (
            searchItem((oriIPsets + i)->srcIP, 's', 1) ||\
            searchItem((oriIPsets + i)->dstIP, 'd', 1) ||\
            searchItem((oriIPsets + i)->srcPort, 's', 0) ||\
            searchItem((oriIPsets + i)->dstPort, 'd', 0) ||\
            checkStatus((oriIPsets + i)->protocol, 's', 1) ||\
            checkStatus((oriIPsets + i)->protocol, 'd', 1) ||\
            checkStatus((oriIPsets + i)->netcard, 's', 0) ||\
            checkStatus((oriIPsets + i)->netcard, 'd', 0)
        )
        {
            amount--;
            continue;
        }
        else
        {
            *(IPsets + cnt) = *(oriIPsets + i);
            cnt++;
            continue;
        }
    }

    return cnt;
}

static bool writeConfig(ConfigInfo * ci)
{
    bool ret = false;

    char* filename;
    int LEN;
    int* totalLines;
    u32 mask;

    if (ci->mode)
    {
        filename = cfgname;
        LEN = LEN_CFG_LINE;
        totalLines = &totalIPLines;
        mask = 0xffffffff;
    }
    else
    {
        filename = portcfgname;
        LEN = LEN_PORT_LINE;
        totalLines = &totalPtLines;
        mask = 0xffff;
    }

    FILE * fp = fopen(filename, "a");
    if (fp)
    {
        char* info = (char*)malloc((LEN + 1) * sizeof(char));
        char * writtenTime = ctime(&(ci->timestamp));
        *(writtenTime + 24) = '\0';
        if (ci->mode)
        {
            char * type = ci->type == 's' ? "srcIP" : "dstIP";
            sprintf(info, "%08x%02x%08x%c  "IPformat"  %5s  %24s", \
                (ci->num & mask), u32time(ci->timestamp), ci->type, \
                format_IP(ci->num & mask), type, writtenTime);
        }
        else
        {
            char * type = ci->type == 's' ? "srcPort" : "dstPort";
            sprintf(info, "%04x%02x%08x%c  %5d  %7s  %24s", \
                (ci->num & mask), u32time(ci->timestamp), ci->type, \
                (ci->num & mask), type, writtenTime);
        }

        fprintf(fp, "%s\n", info);
        free(info);
        fclose(fp);
        ret = true;
        (*totalLines)++;
    }
    else
        printf("Write Failed!\n");

    return ret;
}

static inline bool legalNum(int num, int amount)
{
    return num <= amount && num >= 0;
}

static inline bool legalType(char type)
{
    return type == 's' || type == 'd' || type == 'n';
}

static inline bool legalMode(int mode)
{
    return mode == 0 || mode == 1;
}

void configByFlow(int amount, const Package * IPsets)
{
    int num;
    char type;
    int mode;
    for (int i = 0; i < 2 * amount + 1; i++)
    {
        printf("Please Input Package Number(Input 0 To Quit): ");
        scanf("%2d", &num);
        getchar();
        if (!num)
            break;
        printf("Please Input 's' For Source or 'd' For Destination: ");
        scanf("%c", &type);
        printf("Please Choose '1' for IP or '0' for Port: ");
        scanf("%1d", &mode);
        printf("\n");

        if (legalNum(num, amount) && legalType(type) && legalMode(mode))
        {
            ConfigInfo * ci = (ConfigInfo *) malloc(sizeof(ConfigInfo));
            time(&(ci->timestamp));
            ci->type = type;
            ci->mode = mode;
            if (mode)
                ci->num = type == 's' ? \
                    (IPsets + num - 1)->srcIP : (IPsets + num - 1)->dstIP;
            else
                ci->num = type == 's' ? \
                    (IPsets + num - 1)->srcPort : (IPsets + num - 1)->dstPort;
            // 如果没有，写入文件
            if (!searchItem(ci->num, ci->type, ci->mode))
                writeConfig(ci);
            else
                i--;
            free(ci);
        }
        // 防止错误的输入冲掉正常输入
        else
            i--;
    }
}

bool addConfig(u32 num, char type, int mode)
{
    bool ret = false;

    if (!searchItem(num, type, mode))
    {
        ConfigInfo* ci = (ConfigInfo*)malloc(sizeof(ConfigInfo));
        ci->num = num;
        ci->type = type;
        ci->mode = mode;
        time(&(ci->timestamp));
        writeConfig(ci);
        free(ci);
        printf("Total Items: %d\n", mode ? totalIPLines : totalPtLines);
        ret = true;
    }
    else
        printf("Item Already Exist ERROR!\n");

    return ret;
}

bool deleteConfig(u32 num, char type, int mode)
{
    bool ret = false;
    int line = searchItem(num, type, mode);

    if (line)
    {
        char* filename;
        int* totalLines;
        int LEN;

        if (mode)
        {
            filename = cfgname;
            totalLines = &totalIPLines;
            LEN = LEN_CFG_LINE;
        }
        else
        {
            filename = portcfgname;
            totalLines = &totalPtLines;
            LEN = LEN_PORT_LINE;
        }

        FILE * fpo = fopen(filename, "r");
        if (fpo)
        {
            FILE * fpn = fopen(tmpname, "w");
            if (fpn)
            {
                int currentLine = -1;
                char* buff = (char*)malloc((LEN + 1) * sizeof(char));

                while (currentLine <= *totalLines)
                {
                    if (currentLine == line)
                    {
                        currentLine++;
                        fseek(fpo, (LEN + 1) * sizeof(char), SEEK_CUR);
                        continue;
                    }
                    buff = fgets(buff, LEN + 1, fpo);
                    fprintf(fpn, "%s", buff);
                    currentLine++;
                }

                free(buff);

                fflush(fpn);
                fclose(fpn);
                ret = true;
            }
            else
                printf("Create file failed!\n");
            fflush(fpo);
            fclose(fpo);
        }
        else
            printf("Open file failed!\n");
        
        if (ret)
        {
            remove(filename);
            rename(tmpname, filename);
            (*totalLines)--;
            printf("Total Items: %d\n", *totalLines);
        }
    }
    else
        printf("Can't find the item!\n");

    return ret;
}

void staticIPConfig(void)
{
    printf("Please input IP, type and operation(a for add, d for delete)\n");
    printf("eg: 127.0.0.1 s a\n    192.168.3.1 d d\n");
    unsigned int a[5];
    u32 IP;
    char type, opr;

    scanf("%3u.%3u.%3u.%3u %c %c", &a[0], &a[1], &a[2], &a[3], &type, &opr);
    getchar();
    // printf("%c %c", type, opr);
    IP = (a[0] << 24) + (a[1] << 16) + (a[2] << 8) + a[3];

    if (opr == 'a')
        addConfig(IP, type, 1);
    else if (opr == 'd')
        deleteConfig(IP, type, 1);
    else
        printf("Wrong Command!\n");
}

void staticPortConfig(void)
{
    printf("Please input port, type and operation(a for add, d for delete)\n");
    printf("eg: 65535 s a\n    80 d d\n");
    unsigned int port;
    char type, opr;

    scanf("%i %c %c", &port, &type, &opr);
    getchar();

    if (opr == 'a')
        addConfig(port, type, 0);
    else if (opr == 'd')
        deleteConfig(port, type, 0);
    else
        printf("Wrong Command!\n");
}

bool writeProtCfg(int num, char type)
{
    bool ret = false;
    int offset = type == 's' ? 18 : 30;
    FILE* fp = fopen(protocolcfgname, "r+");
    if (fp)
    {
        fseek(fp, ((LEN_PROTOCOL_LINE + 1) * (num + 1) + offset) * \
            sizeof(char), SEEK_SET);
        if (fgetc(fp) == 'P')
        {
            fseek(fp, ((LEN_PROTOCOL_LINE + 1) * (num + 1) + offset) * \
                sizeof(char), SEEK_SET);
            fprintf(fp, "BAN ");
        }
        else
        {
            fseek(fp, ((LEN_PROTOCOL_LINE + 1) * (num + 1) + offset) * \
                sizeof(char), SEEK_SET);
            fprintf(fp, "PASS");
        }

        fclose(fp);
        ret = true;
    }
    else
        printf("Open Protocol Config File Failed!\n");

    return ret;
}

void protocolConfig(void)
{
    int num;
    char type;
    printf("Please Choose The Number of The Protocol: ");
    scanf("%d", &num);
    getchar();
    printf("Please Input 's' for SRC and 'd' for DST: ");
    scanf("%c", &type);
    getchar();
    writeProtCfg(num, type);
}

void writeNCdCfg(int num, char type)
{
    bool ret = false;
    int offset = type == 's' ? 17 : 29;
    FILE* fp = fopen(netcardcfgname, "r+");
    if (fp)
    {
        fseek(fp, ((LEN_NETCARD_LINE + 1) * (num + 1) + offset) * \
            sizeof(char), SEEK_SET);
        if (fgetc(fp) == 'P')
        {
            fseek(fp, ((LEN_NETCARD_LINE + 1) * (num + 1) + offset) * \
                sizeof(char), SEEK_SET);
            fprintf(fp, "BAN ");
        }
        else
        {
            fseek(fp, ((LEN_NETCARD_LINE + 1) * (num + 1) + offset) * \
                sizeof(char), SEEK_SET);
            fprintf(fp, "PASS");
        }

        fclose(fp);
    }
    else
        printf("Open Net Card Config File Failed!\n");
}

void netcardConfig(void)
{
    int num;
    char type;
    printf("Please Choose The Number of The Net Card: ");
    scanf("%d", &num);
    getchar();
    printf("Please Input 's' for SRC and 'd' for DST: ");
    scanf("%c", &type);
    getchar();
    writeNCdCfg(num, type);
}